/**
 * 
 */
package com.xpec.c4.game.utility;

import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.TimeZone;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONObject;

import com.ms.player.Player;
import com.ms.service.module.zone.BasicZone;
import com.ms.service.module.zone.module.BasicZoneModule;
import com.xpec.c4.game.common.Helper;
import com.xpec.c4.game.time.TimeConstants;
import com.xpec.c4.game.time.TimeSys;
import com.xpec.c4.service.exceptions.MathException;
import com.xpec.c4.service.module.BasicServiceModule;
import com.xpec.c4.service.module.ModuleServer;

/**
 * 
 * 
 * @author lipeilin
 * @2010-9-4下午05:54:57
 * 
 */
public class GameUtility {
	
	/**
	 * for cash用
	 */
	private static final Log log = LogFactory.getLog(GameUtility.class);
	
	/** 時間單位的數值清單 */
	private static ArrayList<Long> timeUnitValueList = new ArrayList<Long>();

	/** 時間單位的文字清單 */
	private static ArrayList<String> timeUnitTextList = new ArrayList<String>();

	/**
	 * 建構時初始化
	 */
	static {
		// 時間單位的數值清單
		timeUnitValueList.add(TimeConstants.MILLI_SECOND_PRE_DAY);
		timeUnitValueList.add(TimeConstants.MILLI_SECOND_PRE_HOUR);
		timeUnitValueList.add(TimeConstants.MILLI_SECOND_PRE_MINUTE);
		timeUnitValueList.add(TimeConstants.MILLI_SECOND_PRE_SECOND);

		// 時間單位的文字清單
		timeUnitTextList.add("d");
		timeUnitTextList.add("h");
		timeUnitTextList.add("m");
		timeUnitTextList.add("s");
	};

	/**
	 * 檢查字串內容，如果是 null 就傳回 null 字串
	 * 
	 * @param str
	 *            輸入的字串
	 * @return 如果 str 是 null 就傳回 "null"<br>
	 *         如果 str 有值就傳回 str
	 */
	public static String safeStr(String str) {
		if (str != null) {
			return str;
		}

		return "null";
	}

	/** 日期格式字串 詳細 */
	public static final String TIME_FORMAT_FULL = "yyyy/M/d HH:mm:ss";

	/** 日期格式字串 只有時間 */
	public static final String TIME_FORMAT_TIME = "HH:mm:ss";

	/** 日期格式字串 時間.毫秒 */
	public static final String TIME_FORMAT_TIME_MS = "HH:mm:ss.SSS";

	/**
	 * 將指定的時間轉成字串<br>
	 * 
	 * 輸出格式: yyyy/M/d HH:mm:ss
	 * 
	 * @param time
	 *            指定的時間 (ms)
	 * @param formatPattern
	 *            時間格式字串，可以用 {@link GameUtility#TIME_FORMAT_FULL}
	 * @return 時間字串
	 */
	public static String timeToString(long time, String formatPattern) {
		String timeStr = "";
		Date date = new Date(time);
		SimpleDateFormat sdf = new SimpleDateFormat(formatPattern);

		timeStr = sdf.format(date);

		return timeStr;
	}

	/**
	 * 檢查指定的字串是否為一組數字
	 * 
	 * @param str
	 *            要檢查的字串
	 * @return true 是數值<br>
	 *         false 不是數值
	 */
	public static boolean isNumeric(String str) {
		if (!isHaveString(str)) {
			return false;
		}

		try {
			Double.parseDouble(str);
		} catch (NumberFormatException nfe) {
			return false;
		}
		return true;
	}
	
	/**
	 * 字串轉成 int
	 * 
	 * @param numStr
	 * @return 如果是 int 形式的字串回傳 int<br>
	 *         如果不是正確的字串回傳 0
	 */
	public static int StringToInt(String numStr) {
		int value = 0;

		if (!isNumeric(numStr)) {
			return value;
		}

		try {
			value = Integer.valueOf(numStr);
		} catch (NumberFormatException e) {
			return value;
		}

		return value;
	}

	/**
	 * 取得指定範圍內的 integer 亂數
	 * 
	 * @param v1
	 *            亂數範圍的最小值
	 * @param v2
	 *            亂數範圍的最大值
	 * @return 亂數的結果
	 */
	public static int randomInt(int v1, int v2) {
		return Helper.randomInt(v1, v2 + 1);
	}

	/**
	 * 取得指定範圍內的 long 亂數
	 * 
	 * @param v1
	 *            亂數範圍的最小值
	 * @param v2
	 *            亂數範圍的最大值
	 * @return 亂數的結果
	 */
	public static long randomLong(long v1, long v2) {
		return Helper.randomLong(v1, v2 + 1);
	}

	/**
	 * 確認字串是不是有內容的<br>
	 * 
	 * 包含了 null，長度是否為 0 的檢查
	 * 
	 * @param string
	 *            字串
	 * @return true: 是個有內容的字串
	 *         false: 沒有內容
	 */
	public static boolean isHaveString(String string) {
		if (string == null) {
			return false;
		}

		if (string.isEmpty()) {
			return false;
		}

		return true;
	}
	
	/**
	 * 取一個字串的右邊 n 個字
	 * 
	 * @param str
	 *            來源字串
	 * @param len
	 *            要取的長度
	 * @return
	 */
	public static String rightOfString(String str, int len) {
		if (len > str.length()) {
			len = str.length();
		}

		return str.substring(str.length() - len);
	}

	/**
	 * 時間一秒變成 GregorianCalendar 物件
	 * 
	 * @param currentTime
	 *            目前時間 (ms)
	 * @return GregorianCalendar 物件
	 */
	public static GregorianCalendar timeToCalendar(long currentTime) {
		TimeZone curTimeZone = TimeZone.getDefault();
		GregorianCalendar calendar = new GregorianCalendar(curTimeZone);
		calendar.setTimeInMillis(currentTime);

		return calendar;
	}

	/**
	 * 取得每日固定時間觸發的事件下一次觸發的時間
	 * 
	 * @param triggerTime
	 *            每天固定觸發的時間，要轉成 ms<br>
	 *            00:00 觸發就是 0<br>
	 *            02:00 觸發就是 2 * TimeConstants.MILLI_SECOND_PRE_HOUR<br>
	 *            02:20 觸發就是 (2 * MILLI_SECOND_PRE_HOUR) + (20 *
	 *            MILLI_SECOND_PRE_MINUTE)
	 * @return 下一次觸發時間 (ms)
	 * @see com.xpec.c4.game.time.TimeConstants
	 */
	public static long getDailyEventNextTriggerTime(long triggerTime) {
		// current time
		long currentTime = TimeSys.currentTimeMills();

		// current time to calendar
		TimeZone curTimeZone = TimeZone.getDefault();
		GregorianCalendar calendar = new GregorianCalendar(curTimeZone);
		calendar.setTimeInMillis(currentTime);

		// 去掉日期以下的尾數，就是當天 00:00 的時間
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		long dailyBeginTime = calendar.getTimeInMillis();

		long todayTriggerTime = dailyBeginTime + triggerTime;
		long nextDayTriggerTime = dailyBeginTime + TimeConstants.MILLI_SECOND_PRE_DAY + triggerTime;

		if (currentTime > todayTriggerTime) {
			return nextDayTriggerTime;
		}

		return todayTriggerTime;
	}
	
	/**
	 * 將時間值中的時間去掉，只留下日期，也就是當天 00:00 的時間
	 * 
	 * @param timeValue
	 *            系統時間值
	 * @return 只剩下日期的時間值
	 */
	public static long timeValueKeepDate(long timeValue) {
		TimeZone curTimeZone = TimeZone.getDefault();
		GregorianCalendar calendar = new GregorianCalendar(curTimeZone);
		calendar.setTimeInMillis(timeValue);

		// 去掉日期以下的尾數，就是當天 00:00 的時間
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		long dateValue = calendar.getTimeInMillis();
		return dateValue;
	}
	
	/**
	 * 取得目前的日期值, 其實就是去掉時間部份的系統時間
	 * 
	 * @return 去掉時間部份的系統時間
	 */
	public static long getCurrentDateLong() {
		long curTime = TimeSys.currentTimeMills();
		long curDate = timeValueKeepDate(curTime);

		return curDate;
	}

	/**
	 * 比對兩個時間之間是否有跨日
	 * 
	 * @param time1
	 *            用來比對的時間1 (比較小的)
	 * @param time2
	 *            用來比對的時間2 (比較大的)
	 * @param hourRegulate           
	 *            跨日小時依據，24小時制 (0-23)
	 * @return true: 有跨日<br>
	 *         false: 沒有跨日
	 */
	public static boolean isHaveCrossDay(long time1, long time2 , int hourRegulate) {
		TimeZone curTimeZone = TimeZone.getDefault();

		GregorianCalendar calendar1 = new GregorianCalendar(curTimeZone);
		calendar1.setTimeInMillis(time1);

		GregorianCalendar calendar2 = new GregorianCalendar(curTimeZone);
		calendar2.setTimeInMillis(time2);

		int y1 = calendar1.get(Calendar.YEAR);
		int m1 = calendar1.get(Calendar.MONTH);
		int d1 = calendar1.get(Calendar.DAY_OF_MONTH);
		int h1 = calendar1.get(Calendar.HOUR_OF_DAY);

		int y2 = calendar2.get(Calendar.YEAR);
		int m2 = calendar2.get(Calendar.MONTH);
		int d2 = calendar2.get(Calendar.DAY_OF_MONTH);
		int h2 = calendar2.get(Calendar.HOUR_OF_DAY);

		// 比對年
		if (y1 != y2) {
			return true;
		}
		
		//比對月
		if (m1 != m2) {
			return true;
		}
		
		// 如果不同一天
		if (d1 != d2) {
			//如果只差一天就要比較小時
			if((d2 - d1) == 1){
				if(h1 < hourRegulate || h2 >= hourRegulate){
					return true;
				}
			}else{
				return true;
			}
		}else{
			//同一天比較小時
			if(h1 < hourRegulate && h2 >= hourRegulate){
				return true;
			}
		}

		return false;
	}
	
	/**
	 * 取得跨日天數 
	 * @param time1
	 * @param time2
	 * @param hourRegulate = 指定跨日小時 (24小時制)
	 * @return
	 */
	public static long getCrossDay(long time1, long time2, int hourRegulate){
		TimeZone curTimeZone = TimeZone.getDefault();

		GregorianCalendar calendar1 = new GregorianCalendar(curTimeZone);
		calendar1.setTimeInMillis(time1);

		GregorianCalendar calendar2 = new GregorianCalendar(curTimeZone);
		calendar2.setTimeInMillis(time2);

		//留下天
		int hour1 =  calendar1.get(Calendar.HOUR_OF_DAY);
		calendar1.set(Calendar.HOUR_OF_DAY, hourRegulate);
		calendar1.set(Calendar.MINUTE, 0);
		calendar1.set(Calendar.SECOND, 0);
		calendar1.set(Calendar.MILLISECOND,0);
		//如果小於 指定的小時數 等於是上一天
		if(hour1 < hourRegulate){
			calendar1.add(Calendar.DATE, -1);
		}
		
		//留下天
		int hour2 =  calendar1.get(Calendar.HOUR_OF_DAY);
		calendar2.set(Calendar.HOUR_OF_DAY, hourRegulate);
		calendar2.set(Calendar.MINUTE, 0);
		calendar2.set(Calendar.SECOND, 0);
		calendar2.set(Calendar.MILLISECOND,0);
		//如果小於 指定的小時數 等於是上一天
		if(hour2 < hourRegulate){
			calendar2.add(Calendar.DATE, -1);
		}
		
		long day = (calendar2.getTimeInMillis() - calendar1.getTimeInMillis() ) / TimeConstants.MILLI_SECOND_PRE_DAY;
		
		return day;
	}

	/**
	 * 將剩時間轉成給人看的時間字串
	 * 
	 * @param remainTime
	 *            剩餘時間 (ms)
	 * @return 剩餘時間字串
	 */
	public static String remainTimeToString(long remainTime) {
		if (remainTime <= 0) {
			return "0";
		}

		String remainTimeString = "";

		int imax = timeUnitValueList.size();
		for (int i = 0; i < imax; i++) {
			long unit = timeUnitValueList.get(i);
			if (remainTime > unit) {
				long mod = remainTime % unit;
				long cnt = (remainTime - mod) / unit;
				remainTimeString += (cnt + timeUnitTextList.get(i) + " ");
				remainTime = mod;
			}
		}

		if (remainTime > 0) {
			remainTimeString += remainTime;
		}

		return remainTimeString;
	}

	/**
	 * 去掉重复的
	 * 
	 * @param array
	 * @return
	 * 
	 * @author lipeilin
	 * @2010-9-4下午05:54:22
	 */
	public static Integer[] distinctArray(Integer[] array) {
		HashSet<Integer> existIndex = new HashSet<Integer>();
		for (Integer i : array) {
			existIndex.add(i);
		}
		Integer[] temp = existIndex.toArray(new Integer[0]);
		return temp;
	}

	/**
	 * 把集合转为int[]
	 * 
	 * @param existIndex
	 * @return
	 * 
	 * @author lipeilin
	 * @2010-9-4下午06:00:36
	 */
	public static Integer[] collectionToIntArray(Collection<Integer> existIndex) {
		return existIndex.toArray(new Integer[0]);
	}

	/**
	 * 加法，并做了溢出处理
	 * 
	 * @param value1
	 * @param value2
	 * @return
	 * 
	 * @author lipeilin
	 * @2010-9-4下午06:10:46
	 */
	public static float add(float value1, float value2) {
		float sum = value1 + value2;

		// 正数越界
		if (Float.isInfinite(sum)) {
			throw new MathException();
		}

		// 负数越界
		if (Float.isNaN(sum)) {
			throw new MathException();
		}

		return sum;
	}

	/**
	 * 加法，并做了溢出处理
	 * 
	 * @param value1
	 * @param value2
	 * @return
	 * 
	 * @author lipeilin
	 * @2010-9-4下午06:11:00
	 */
	public static int add(int value1, int value2) {
		int sum = value1 + value2;

		// 正数越界
		if (value1 > 0 && value2 > 0 && sum < 0) {
			throw new MathException();
		}

		// 负数越界
		if (value1 < 0 && value2 < 0 && sum > 0) {
			throw new MathException();
		}

		return sum;
	}

	/**
	 * test x,y is in the range
	 * 
	 * @param gridX1
	 * @param gridY1
	 * @param gridX2
	 * @param gridY2
	 * @param gridX
	 * @param gridY
	 * @return
	 */
	public static boolean inRange(int gridX1, int gridY1, int gridX2, int gridY2, int gridX, int gridY) {

		if (gridX < Math.min(gridX1, gridX2)) {
			return false;
		}

		if (gridX > Math.max(gridX1, gridX2)) {
			return false;
		}

		if (gridY < Math.min(gridY1, gridY2)) {
			return false;
		}

		if (gridY > Math.max(gridY1, gridY2)) {
			return false;
		}

		return true;
	}

	/**
	 * test x,y is in the range
	 * 
	 * @param gridX1
	 * @param gridY1
	 * @param gridX2
	 * @param gridY2
	 * @param gridX
	 * @param gridY
	 * @return
	 */
	public static boolean inRange(float x1, float y1, float x2, float y2, float x, float y) {

		if (x < Math.min(x1, x2)) {
			return false;
		}

		if (x > Math.max(x1, x2)) {
			return false;
		}

		if (y < Math.min(y1, y2)) {
			return false;
		}

		if (y > Math.max(y1, y2)) {
			return false;
		}

		return false;
	}

	/**
	 * 將 long 轉成 Date
	 * 
	 * @param longTime
	 *            代表時間值的 long
	 * @return Date object
	 */
	public static Date longToDate(long longTime) {
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTimeInMillis(longTime);
		return calendar.getTime();
	}

	/**
	 * 取得目前的日期時間
	 * 
	 * @return 日期時間物件
	 */
	public static Date getCurrentDate() {
		long curTime = TimeSys.currentTimeMills();
		return longToDate(curTime);
	}

	/**
	 * 格式化輸出數字
	 * 
	 * @param number
	 *            數字
	 * @param pattern
	 *            輸出格式，請參考 DecimalFormat 的說明
	 * @return
	 */
	public static String formatNumber(int number, String pattern) {
		NumberFormat formatter = new DecimalFormat(pattern);

		String result = formatter.format(number);

		return result;
	}

	/**
	 * 取得指定的 zone module
	 * <p>
	 * module 必須存在於目前的 process 裡面才取得到
	 * 
	 * @param zoneId
	 *            zone id
	 * @return 成功: zone module, 失敗: null
	 */
	public static BasicZoneModule getZoneModule(String zoneId) {
		BasicServiceModule basicServiceModule = ModuleServer.getInstance().getBasicServiceModule(zoneId);
		if (basicServiceModule == null) {
			return null;
		}

		if (!(basicServiceModule instanceof BasicZoneModule)) {
			return null;
		}

		return (BasicZoneModule) basicServiceModule;
	}

	/**
	 * 取得指定的 zone
	 * <p>
	 * zone 必須存在於目前的 process 裡面才取得到
	 * 
	 * @param zoneId
	 *            zone id
	 * @return 成功: zone, 失敗: null
	 */
	public static BasicZone getZone(String zoneId) {
		BasicZoneModule zoneModule = getZoneModule(zoneId);
		if (zoneModule == null) {
			return null;
		}

		BasicZone zone = zoneModule.getZone();

		return zone;
	}

	/**
	 * 透過 zone 取得指定的玩家資料
	 * <p>
	 * zone 必須存在於目前的 process 裡面才取得到
	 * 
	 * @param zoneId
	 *            zone id
	 * @param playerGuid
	 *            玩家 id
	 * @return 成功: player, 失敗: null
	 */
	public static Player getPlayerFromZone(String zoneId, GUID playerGuid) {
		BasicZone zone = getZone(zoneId);
		if (zone == null) {
			return null;
		}

		Player player = zone.getPlayer(playerGuid);

		return player;
	}

	/**
	 * int 用的次方計算
	 * 
	 * @param base
	 *            基礎值
	 * @param exp
	 *            n次方
	 * @return 結果
	 */
	public static int ipow(int base, int exp) {
		int result = 1;
		while (exp > 0) {
			if ((exp & 1) == 1) {
				result *= base;
			}
			exp >>= 1;
			base *= base;
		}

		return result;
	}

	/**
	 * 將 int 轉成 bit 字串
	 * <p>
	 * ex: 50455 => 1100 0101 0001 0111
	 * 
	 * @param num
	 *            要轉換的 int
	 * @return bit 字串
	 */
	public static String intToBitString(int num) {
		StringBuilder sb = new StringBuilder();

		sb.append(Integer.toString(num, 2));

		int imax = sb.length();
		int bitCnt = 0;
		for (int i = imax - 1; i >= 0; i--) {
			bitCnt++;
			if (bitCnt == 4) {
				sb.insert(i, " ");
				bitCnt = 0;
			}
		}

		int fillZeroCnt = 4 - (imax % 4);
		sb.insert(0, StringUtils.repeat("0", fillZeroCnt));

		return sb.toString();
	}

	/**
	 * 根據總數和每頁顯示數量計算總頁數
	 * 
	 * @param totalCount
	 *            總數
	 * @param countPerPage
	 *            每頁顯示數量
	 * @return 總頁數
	 */
	public static int calculatePageCount(int totalCount, int countPerPage) {
		int mod = (totalCount % countPerPage);

		int count = (totalCount - mod) / countPerPage;

		if (mod > 0) {
			count++;
		}

		return count;
	}

	/**
	 * 交換 ArrayList 中的兩個元素
	 * 
	 * @param objList
	 *            ArrayList
	 * @param indexA
	 *            位置A
	 * @param indexB
	 *            位置B
	 */
	public static <T> void exchangeArrayListElement(ArrayList<T> objList, int indexA, int indexB) {
		if (objList == null) {
			return;
		}

		if (indexA < 0 || indexA >= objList.size()) {
			return;
		}

		if (indexB < 0 || indexB >= objList.size()) {
			return;
		}

		T tmp = objList.get(indexA);
		objList.set(indexA, objList.get(indexB));
		objList.set(indexB, tmp);
	}

	/**
	 * 從 List 中隨機取得一個元素
	 * 
	 * @param objList
	 * @return 隨機元素
	 */
	public static <T> T getRandomElement(List<T> objList) {
		if (objList == null) {
			return null;
		}

		if (objList.size() == 0) {
			return null;
		}

		int idx = Helper.randomInt(0, objList.size());
		T obj = objList.get(idx);

		return obj;
	}
	
	/**
	 * 從List中隨機取得一個index
	 * @param objList
	 * @return
	 */
	public static <T> int getListRandomIndex(List<T> objList){
		if (objList == null) {
			return -1;
		}

		if (objList.size() == 0) {
			return -1;
		}

		return Helper.randomInt(0, objList.size());
	}

	/**
	 * 從 HashMap 中隨機取得一個元素
	 * 
	 * @param objMap
	 *            HashMap
	 * @return 隨機元素
	 */
	public static <K, V> V getRandomElement(HashMap<K, V> objMap) {
		if (objMap == null) {
			return null;
		}

		if (objMap.size() == 0) {
			return null;
		}

		ArrayList<V> valueList = new ArrayList<V>(objMap.values());
		V value = getRandomElement(valueList);

		return value;
	}

	/**
	 * 先把除數減掉餘數後再整除的結果
	 * 
	 * @param divisor
	 *            除數
	 * @param dividend
	 *            被除數
	 * @return 整除的結果
	 */
	public static long divisible(long divisor, long dividend) {
		return (divisor - (divisor % dividend)) / dividend;
	}

	/**
	 * 將 exception 轉成 call stack 字串
	 * 
	 * @param e
	 *            exception object
	 * @return call stack 字串
	 */
	public static String eToString(Exception e) {
		// stack list
		StackTraceElement[] eList = e.getStackTrace();
		StringBuilder sb = new StringBuilder();

		// 產生標題
		sb.append(e.toString());
		sb.append(" (");
		sb.append(e.getLocalizedMessage());
		sb.append(")\n");

		// 產生 call stack
		for (StackTraceElement element : eList) {
			sb.append("	at " + element.toString());
			sb.append("\n");
		}

		return sb.toString();
	}

	/**
	 * 取得 Array 裡的物件數量
	 * 
	 * @param list
	 *            Array, null 時會回傳 0
	 * @return 數量
	 */
	public static <T> int getArraySize(T[] list) {
		if (list == null) {
			return 0;
		}

		return list.length;
	}

	/**
	 * 取得 ArrayList 裡的物件數量
	 * 
	 * @param list
	 *            ArrayList, null 時會回傳 0
	 * @return 數量
	 */
	public static <T> int getArrayListSize(ArrayList<T> list) {
		if (list == null) {
			return 0;
		}

		return list.size();
	}

	/**
	 * 取得 Map 裡的物件數量
	 * 
	 * @param map
	 *            HashMap, null 時會回傳 0
	 * @return 數量
	 */
	public static <K, V> int getMapSize(AbstractMap<K, V> map) {
		if (map == null) {
			return 0;
		}

		return map.size();
	}

	/**
	 * 取得 Set 裡的物件數量
	 * 
	 * @param set
	 *            set, null 時會回傳 0
	 * @return 數量
	 */
	public static <K, V> int getSetSize(AbstractSet<K> set) {
		if (set == null) {
			return 0;
		}

		return set.size();
	}

	/**
	 * 取得 queue 裡的物件數量
	 * 
	 * @param queue
	 *            queue, null 時會回傳 0
	 * @return 數量
	 */
	public static <T> int getQueueSize(Queue<T> queue) {
		if (queue == null) {
			return 0;
		}

		return queue.size();
	}

	/**
	 * 將 array 中的元素順序打亂
	 * 
	 * @param array
	 *            各種 aray
	 */
	public static <T> void shuffleArray(T[] array) {
		Random rnd = ThreadLocalRandom.current();
		for (int i = array.length - 1; i > 0; i--) {
			// 亂數選一個位置
			int index = rnd.nextInt(i + 1);

			// 與目前位置交換
			if (index != i) {
				T a = array[index];
				array[index] = array[i];
				array[i] = a;
			}
		}
	}

	/**
	 * 將檔案長度轉成可讀性高的字串
	 * 
	 * @param bytes
	 *            長度
	 * @param si
	 *            是否使用 SI base 單位
	 * @return 可讀性高的大小字串
	 * @see http://stackoverflow.com/questions/3758606
	 */
	public static String humanReadableByteCount(long bytes, boolean si) {
		int unit = si ? 1000 : 1024;
		if (bytes < unit) {
			return bytes + " B";
		}
		int exp = (int) (Math.log(bytes) / Math.log(unit));
		String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
		return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
	}
	
	/**
	 * 取得目前正在執行的 function 名稱
	 * 
	 * @return
	 */
	public static String getCurMethodName() {
		return Thread.currentThread().getStackTrace()[2].getMethodName();
	}
	

	/**
	 * 合併兩個 byte array 成一個新的 byte array
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static byte[] appendByteArrays(byte[] a, byte[] b) {
		byte[] result = new byte[a.length + b.length];
		System.arraycopy(a, 0, result, 0, a.length);
		System.arraycopy(b, 0, result, a.length, b.length);
		return result;
	}
	
	
	/**
	 *  取的下一次的刷新時間 (24小時刷新一次)
	 *  now = 2016/04/28 16:57:00
	 *  ex: timeFormate = 01:00:00
	 *  LastFlushTime = 2016/04/28 01:00:00
	 * @param timeFormate 時間格式 Ex : 00:00:00 / 15:15:15
	 * @return
	 */
	public static Date getEventLastFlushTime(String timeFormate){
		String flushTime = timeFormate;
		String[] timeArray = flushTime.split("-");

		Date now = new Date();
		Date date = new Date();
		date.setHours(Integer.parseInt(timeArray[0]));
		date.setMinutes(Integer.parseInt(timeArray[1]));
		date.setSeconds(Integer.parseInt(timeArray[2]));
		
		if(now.getTime() - date.getTime() > 0){
			date.setHours(date.getHours() + 24);
		}
		date.setHours(date.getHours() -24);
		
		SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd");
		try {
			date = formater.parse(formater.format(date));
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return date;
	}
	
	
	/**
	 *  取的下一次的刷新時間 (24小時刷新一次)
	 *  now = 2016/04/28 16:57:00
	 *  ex: timeFormate = 01:00:00
	 *  LastFlushTime = 2016/04/29 01:00:00
	 * @param timeFormate 時間格式 Ex : 00:00:00 / 15:15:15
	 * @return
	 */
	public static Date getEventNextFlushTime(String timeFormate){
			Date date = getEventLastFlushTime(timeFormate);
			date.setDate(date.getDate() + 1);
			return date;
	}
	
	/**
	 * 把毫秒轉成秒 (除1000)
	 * @param millisecond
	 * @return
	 */
	public static long transformTimeMilliToSec(long millisecond) {
		if(millisecond == 0){
			return 0;
		}
		return millisecond / TimeConstants.MILLI_SECOND_PRE_SECOND; 
	}
	
	/**
	 * 將錯誤訊息加上 exception 的方式寫到 log 中
	 * 
	 * @param log4jObj
	 * @param ErrorMessage
	 */
	public static void logErrorException(Log log4jObj, String ErrorMessage) {
		log4jObj.error(ErrorMessage, new RuntimeException(ErrorMessage));
	}
	
	/**
	 * 將日期轉成 cron Expression 
	 * 例如 每個禮拜三12點要執行
	 * cron = 	0 0 12 ? * 4 *
	 * @param date
	 * @return
	 */
	public static String getCronExpressionByDate(Date date){
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		String seconds = "0";
		String minutes = String.valueOf(calendar.get(Calendar.MINUTE));
		String hours = String.valueOf(calendar.get(Calendar.HOUR_OF_DAY));
		String dayOfMonth = "?";
		String month = "*";
		String dayOfWeek = String.valueOf(calendar.get(Calendar.DAY_OF_WEEK));
		String year = "*";
		String cronExpression = String.format("%s %s %s %s %s %s %s", 
								seconds,
								minutes,
								hours,
								dayOfMonth,
								month,
								dayOfWeek,
								year
								);
		return cronExpression;
	}

	/**
	 * 取得倒數第 n 個 0 base 的索引值<br>
	 * 方便程式閱讀, 減少計算邏輯上麻煩的 +1 -1 造成的錯誤.
	 * 
	 * @param total
	 *            總數, 必須 >= 1
	 * @param lastN
	 *            倒數第幾個, 必須 >= 1
	 * @return
	 */
	public static int getLastNIndex(int total, int lastN) {
		if (total <= 1) {
			return 0;
		}

		if (lastN < 1) {
			lastN = 1;
		}

		int index = total - lastN;
		if (index < 0) {
			index = 0;
		}

		return index;
	}
	
	/**
	 * 安全判斷兩字串是否相等
	 * 
	 * @param string1
	 * @param string2
	 * @return
	 */
	public static boolean safeStringEquals(String string1, String string2) {
		if (string1 == null)
			string1 = "";

		if (string2 == null)
			string2 = "";

		return string1.equals(string2);
	}

	/**
	 * 把Null改成空字串
	 * @param str
	 * @return
	 */
	public static String nullToEmptyString(String str){
		if(str == null){
			return "";
		}
		return str;
	}
	
	/**
	 * 可忽略大小寫判斷字串是否包含
	 * @param baseString
	 * @param checkString
	 * @param isIgnoreCase
	 * @return
	 */
	public static boolean containString(String baseString, String checkString, boolean isIgnoreCase){
		if(isIgnoreCase){
			return baseString.toLowerCase().contains(checkString.toLowerCase());
		}
		else{
			return baseString.contains(checkString);
		}
	}
	
	/**
	 * 回傳一個Date 
	 * 只有到日期
	 * 把 時分秒 毫秒歸零
	 * @param date
	 * @return
	 */
	public static Date getDateOnly(Date date){
		date = DateUtils.setHours(date, 0);
		date = DateUtils.setMinutes(date, 0);
		date = DateUtils.setSeconds(date, 0);
		date = DateUtils.setMilliseconds(date, 0);
		return date;
	}
	
	/**
	 * 帶入 時 分 秒 毫秒 ，回傳一個當日的某時間點的timestamp
	 * @param hour
	 * @param min
	 * @param sec
	 * @param millisec
	 * @return
	 */
	public static long getTimeStamp(int hour , int min , int sec , int millisec){
		Calendar calendar = Calendar.getInstance();

		// 去掉日期以下的尾數，就是當天 00:00 的時間
		calendar.set(Calendar.HOUR_OF_DAY, hour);
		calendar.set(Calendar.MINUTE, min);
		calendar.set(Calendar.SECOND, sec);
		calendar.set(Calendar.MILLISECOND, millisec);
		
		return calendar.getTimeInMillis();
	}
	
	/**
	 * 寫Cash異動的console Log
	 * @param point
	 * @param accountId
	 * @param cashBefore
	 * @param cashChangeValue
	 * @param cashAfter
	 */
	public static void writeCashUpdateConsoleLog(String point, String accountId, long cashBefore, long cashChangeValue, long cashAfter) {
		if (log.isInfoEnabled()) {

			StringBuilder builder = new StringBuilder();
			builder.append("[CashUpdateLog] ");
			builder.append("[");
			builder.append(point);
			builder.append("]");

			builder.append(" AccountId=");
			builder.append(accountId);

			builder.append(" CashChangeBefore=");
			builder.append(cashBefore);

			builder.append(" CashChangeValue=");
			builder.append(cashChangeValue);

			builder.append(" CashChangeAfter=");
			builder.append(cashAfter);
		
			log.info(builder.toString());
		}
		
	}
	
	/**
	 * 看看字串是否可以轉成數字
	 * @param str
	 * @return
	 */
	public static boolean checkStringIsNumeric(String str){
		if (str == null) {
			return false;
		}
		int length = str.length();
		if (length == 0) {
			return false;
		}
		int i = 0;
		if (str.charAt(0) == '-') {
			if (length == 1) {
				return false;
			}
			i = 1;
		}
		for (; i < length; i++) {
			char c = str.charAt(i);
			if (c < '0' || c > '9') {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * 用反射的功能取得指定方法
	 * 
	 * @param target
	 * @param methodName
	 * @param params
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Method getMethod(Object target, String methodName, Class... params) {
		if (!isHaveString(methodName)) {
			return null;
		}

		Method mth = null;
		try {
			if (target instanceof java.lang.Class) {
				mth = ((Class) target).getDeclaredMethod(methodName, params);
				mth.setAccessible(true);
			} else {
				mth = target.getClass().getMethod(methodName, params);
			}
		} catch (Exception e) {
			if (log.isErrorEnabled()) {
				log.error("get method fail.", e);
			}
		}

		return mth;
	}
	
	/**
	 * 檢查是否為正確的 json 格式
	 * 
	 * @param test
	 * @return
	 */
	public static boolean isJsonFormat(String test) {
		if (!isHaveString(test)) {
			return false;
		}

		try {
			new JSONObject(test);
		} catch (Exception ex) {
			try {
				new JSONArray(test);
			} catch (Exception ex1) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 方便寫入 error console log 的共用功能
	 * 
	 * @param log
	 * @param errorLogText
	 * @param curMethodName
	 *            {@link GameUtility #getCurMethodName()}
	 * @param playerId
	 * @param coopId
	 * @param sessionId
	 */
	public static void failErrorLog(Log log, String errorLogText, String curMethodName, GUID playerId,
			String coopId, GUID sessionId) {
		StringBuilder sb = new StringBuilder();
		sb.append(curMethodName + "()");
		sb.append(" " + errorLogText);

		if (playerId != null) {
			sb.append(" p=" + playerId);
		}

		if (coopId != null) {
			sb.append(" coop=" + coopId);
		}

		if (sessionId != null) {
			sb.append(" s=" + sessionId);
		}

		log.error(sb.toString());
	}
}
